
package com.joysuch.wwyt.core.sqlparser.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import com.joysuch.wwyt.core.sqlparser.SqlParserInterceptor;
import com.joysuch.wwyt.dataperm.context.DataPermContext;
import com.joysuch.wwyt.dataperm.context.DataPermContextHolder;
import com.joysuch.wwyt.dataperm.context.DataPermRegisterInfo;
import com.joysuch.wwyt.dataperm.context.DataPermRegisterInfoContext;
import com.joysuch.wwyt.dataperm.enums.DataPermissionFilterType;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SetOperationList;
import net.sf.jsqlparser.statement.select.SubSelect;
import net.sf.jsqlparser.statement.select.WithItem;
import net.sf.jsqlparser.statement.values.ValuesStatement;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DataPermissionSqlParserInterceptor implements SqlParserInterceptor {

    private static final Logger logger = LoggerFactory.getLogger("datapermission");

    @Override
    public void intercept(Statement parse) {
        if (parse instanceof Select) {
            long start = System.currentTimeMillis();
            SelectBody body = ((Select) parse).getSelectBody();
            processSelectBody(body);
            long end = System.currentTimeMillis();
            long time = end - start;
            if (time > 50 || DataPermContextHolder.isPrintDevLog()) {
                logger.debug("{}ms | {}", end - start, parse.toString());
            }
        }
    }

    @Override
    public boolean support() {
        return DataPermContextHolder.isDataPermEnabled();
    }

    protected void processPlainSelect(PlainSelect plainSelect) {
        // 原where表达式
        Expression where = plainSelect.getWhere();

        List<Join> joins = plainSelect.getJoins();
        if (where != null) {
            // 先递归处理where 中的嵌套条件
            processWhereCondition(where);
        }
        if (shouldIntercept(plainSelect.getFromItem())) {
            // 需要拦截
            where = addFilterToWhereMaybe(where, plainSelect.getFromItem(), plainSelect);
        }
        if (joins != null) {
            int joinSize = joins.size();
            for (int i = 0; i < joinSize; i++) {
                // 拦截过程中可能会动态生成新的join，新生成的不再处理，只考虑当前的
                Join j = joins.get(i);
                if (shouldIntercept(j.getRightItem())) {
                    where = addFilterToWhereMaybe(where, j.getRightItem(), plainSelect);
                }
            }
        }
        // 设置新的where条件
        plainSelect.setWhere(where);
    }

    private void processWhereCondition(Expression where) {
        if (where == null) {
            return;
        }
        where.accept(new ExpressionVisitorAdapter() {

            @Override
            public void visit(SubSelect subSelect) {
                processSelectBody(subSelect.getSelectBody());
            }
        });
    }

    private void processSelectBody(SelectBody body) {
        if (body instanceof PlainSelect) {
            processPlainSelect((PlainSelect) body);
        } else if (body instanceof SetOperationList) {
            // 递归处理
            ((SetOperationList) body).getSelects().forEach(b -> processSelectBody(b));
        } else if (body instanceof ValuesStatement) {

        } else if (body instanceof WithItem) {
            processSelectBody(((WithItem) body).getSelectBody());
        }
    }

    private boolean shouldIntercept(FromItem fromItem) {
        return findRegisterInfo(fromItem) != null;
    }

    private DataPermRegisterInfo findRegisterInfo(FromItem fromItem) {
        String key = fromItem.toString();
        if (fromItem instanceof Table) {
            key = ((Table) fromItem).getName();
        }
        List<DataPermRegisterInfo> info = DataPermRegisterInfoContext.getInfo(key);
        return (info == null || info.isEmpty()) ? null : info.get(0);
    }

    private Expression addFilterToWhereMaybe(Expression where, FromItem fromItem, PlainSelect plainSelect) {
        DataPermRegisterInfo info = findRegisterInfo(fromItem);
        // if()
        if (info.getType() == DataPermissionFilterType.DEFAULT) {
            Expression condition = createExpressionForTable(fromItem.getAlias(), info);
            if (where == null) {
                where = condition;
            } else {
                where = new AndExpression(where, condition);
            }
            return where;
        } else if (info.getType() == DataPermissionFilterType.JOIN_TABLE) {
            // 动态生成join条件
            Join join = new Join();
            join.setInner(true);
            FromItem right = createJoinItem(plainSelect, info);
            join.setRightItem(right);
            join.setOnExpression(createJoinOnExpression(fromItem, right, info));
            plainSelect.addJoins(join);

            Expression condition = createJoinWhereForTable(right.getAlias(), info.getJoinInfo());
            if (where == null) {
                where = condition;
            } else {
                where = new AndExpression(where, condition);
            }
            return where;
        } else {
            // filter sql
            InExpression in = new InExpression();
            in.setLeftExpression(new Column(createColumnName(fromItem.getAlias(), info.getSqlColumn())));
            in.setRightExpression(createSubSelect(info.getFilterSql()));
            if (where == null) {
                where = in;
            } else {
                where = new AndExpression(where, in);
            }
            return where;
        }
    }

    private Expression createSubSelect(String filterSql) {
        filterSql = replaceSqlParams(filterSql);
        logger.debug("after replace filter sql params: {}", filterSql);
        try {
            Select parse = (Select) CCJSqlParserUtil.parse(filterSql);
            SubSelect ss = new SubSelect();
            ss.setSelectBody(parse.getSelectBody());
            return ss;
        } catch (JSQLParserException e) {
            logger.error("解析{}异常", filterSql, e);
        }
        return null;
    }

    private String replaceSqlParams(String filterSql) {
        DataPermContext context = DataPermContextHolder.getContext();
        List<Long> departs = context.getDeparts();
        String inString = StringUtils.join(departs, ",");
        if (inString == null || inString.length() == 0) {
            inString = String.valueOf(Long.MIN_VALUE);
        }
        return filterSql.replace("${departs}", inString);
    }

    private String createColumnName(Alias a, String column) {
        if (a != null) {
            return a.getName() + "." + column;
        }
        return column;
    }

    private Expression createJoinOnExpression(FromItem fromItem, FromItem right, DataPermRegisterInfo info) {
        String columnName = createColumnName(fromItem.getAlias(), info.getSqlColumn());

        EqualsTo condition = new EqualsTo();
        condition.setLeftExpression(new Column(columnName));
        // 设置一个不可能存在的值，查不到
        condition.setRightExpression(new Column(createColumnName(right.getAlias(), info.getJoinInfo()[1])));
        return condition;

    }

    private FromItem createJoinItem(PlainSelect plainSelect, DataPermRegisterInfo info) {
        String alias = createUniqueAlias(plainSelect);
        Table item = new Table();
        item.setAlias(new Alias(alias));
        item.setName(info.getJoinInfo()[0]);
        return item;
    }

    private String createUniqueAlias(PlainSelect plainSelect) {
        List<String> usedAliases = findUsedAliais(plainSelect);
        String prefix = "_join_" + (usedAliases.size() + 1);
        return prefix;
    }

    private List<String> findUsedAliais(PlainSelect plainSelect) {
        List<String> list = new ArrayList(5);
        Alias a = plainSelect.getFromItem().getAlias();
        if (a != null) {
            list.add(a.getName());
        }
        if (plainSelect.getJoins() != null) {
            for (Join j : plainSelect.getJoins()) {
                a = j.getRightItem().getAlias();
                if (a != null) {
                    list.add(a.getName());
                }
            }
        }
        return list;
    }

    private Expression createJoinWhereForTable(Alias alias, String[] joinInfo) {
        return createDataPermExpression(alias.getName() + "." + joinInfo[2]);
    }

    private Expression createExpressionForTable(Alias alias, DataPermRegisterInfo info) {
        String columnName = info.getSqlColumn();
        if (alias != null) {
            columnName = alias.getName() + "." + info.getSqlColumn();
        }
        return createDataPermExpression(columnName);
    }

    private Expression createDataPermExpression(String columnName) {
        DataPermContext context = DataPermContextHolder.getContext();
        List<Long> departs = context.getDeparts();
        if (departs == null || departs.isEmpty()) {
            EqualsTo condition = new EqualsTo();
            condition.setLeftExpression(new Column(columnName));
            // 设置一个不可能存在的值，查不到
            condition.setRightExpression(new LongValue(Long.MIN_VALUE));
            return condition;
        } else if (departs.size() == 1) {
            EqualsTo condition = new EqualsTo();
            condition.setLeftExpression(new Column(columnName));
            condition.setRightExpression(new LongValue(departs.get(0)));
            return condition;
        } else {
            InExpression in = new InExpression();
            in.setLeftExpression(new Column(columnName));
            in.setRightItemsList(
                    new ExpressionList(departs.stream().map(id -> new LongValue(id)).collect(Collectors.toList())));
            return in;
        }
    }

}
